package ngat.oss.simulation.metrics;

import ngat.oss.simulation.*;
import ngat.oss.simulation.metrics.*;

import ngat.util.*;
import ngat.util.logging.*;
import ngat.astrometry.*;
import ngat.phase2.*;
import ngat.icm.*;

import java.io.*;
import java.rmi.*;
import java.util.*;
import java.awt.geom.*;

public class DemandCalculator {

    Site site;
    Phase2Model phase2Model;
    ExecutionHistoryModel historyModel;
    AccountingModel accModel;
    TimingConstraintWindowCalculator tcwc;
    EnvironmentPredictor envModel;    
    ExecutionTimingModel execModel;

    LogProxy logger;

    public DemandCalculator(Site                             site,
		   Phase2Model                      phase2Model,
		   ExecutionHistoryModel            historyModel,
		   AccountingModel                  accModel,
		   TimingConstraintWindowCalculator tcwc,
		   EnvironmentPredictor             envModel,    
		   ExecutionTimingModel             execModel) {

	this.site = site;
	this.phase2Model  = phase2Model;
	this.historyModel = historyModel ;
	this.accModel     = accModel;
	this.tcwc         = tcwc;
	this.envModel     = envModel;
	this.execModel    = execModel;

	Logger slogger=  LogManager.getLogger("SIM");
	logger = new LogProxy("DCALC","", slogger);

    }
    
    public DemandStatistics generateDemandStatistics(long start, long end) throws Exception {

	int binsize = 5*60*1000; // 5 minutes
	int nbin = 24*3600*1000/binsize;// 24H/5 min = 288?
	double load[]  = new double[nbin]; // Total demand
	double cload[] = new double[nbin]; // Crit demand
	double pload[] = new double[nbin]; // Prioritized demand
	for (int i = 0; i < nbin; i++) {
	    load[i]  = 0.0;
	    cload[i] = 0.0;
	    pload[i] = 0.0;
	}

	long time = start;
	
	// Check each valid group in phase2 ODB.
	double sumload = 0.0;
	double sumcload = 0.0;
	double sumpload = 0.0;
	Iterator ip = phase2Model.listProposals();
	while (ip.hasNext()) {
	    
	    Proposal proposal = (Proposal)ip.next();
	    
	    // VETO: Proposal null.
	    if (proposal == null) continue;
	    
	    logger.log(1, "Check proposal "+proposal.getName());
	    
	    // VETO: Proposal outside activation period.        
	    if (proposal.getActivationDate() > time ||
		proposal.getExpiryDate() < time) {
		logger.log(2, "Proposal skipped: PROP_ACTIVATION");
		continue;
	    }
	    
	    // VETO: Proposal used up all allocated time.
	    Accounts acc = accModel.getAccounts(proposal.getFullPath());
	    if (acc == null) {
		logger.log(2, "Proposal skipped: PROP_NO_ACCOUNTS");
		    continue;
	    }
	    
	    Account total = acc.getAccount(AccountingModel.ACCOUNT_TOTAL);
	    if (total == null) {
		logger.log(2, "Proposal skipped: PROP_NO_TIME_ALLOC");
		continue;
	    }	
	    
	    if (total.getUsed() >= total.getAllocated()) {
		logger.log(2, "Proposal skipped: Acc.Total="+total);
		logger.log(2, "Proposal skipped: U="+total.getUsed()+" A="+total.getAllocated());
		logger.log(2, "Proposal skipped: PROP_TIME_ALLOC_EXCEEDED");
		continue;
	    }
	    
	    
	    Iterator iGroup = proposal.listAllGroups();		
	    while (iGroup.hasNext()) {
		
		Group group = (Group)iGroup.next();
		
		ExecutionStatistics hist = historyModel.getExecutionStatistics(group);
		long xt = execModel.getExecTime(group);
		
		// work out for each group (type-specific) what windows does it have
		// which start in (t1,t2) - they may end in (t1,t2) or may overrun.
		
		List windows = tcwc.listFeasibleWindows(group,
							hist,
							start, 
							end);
		
		if (windows == null)
		    continue;
		
		logger.log(1, "Found "+windows.size()+" windows in specified time span");
		
		Iterator it = windows.iterator();
		while (it.hasNext()) {
		    
		    TimeWindow w = (TimeWindow)it.next();
		    
		    logger.log(1, "Start RTC Calc for Group: "+group.getName()+" in window: "+w);
		    		    
		    // Now prune the start time to: time
		    w.start = Math.max(w.start, start);
		    TimeWindow fullwin = new TimeWindow(w.start, w.end);

		    // Now prune back to end - we dont need details beyond then
		    w.end   = Math.min(w.end,   end);

		    TimeWindow srcwin = new TimeWindow(Math.max(start,w.start), w.end);
		    // arent start and w.start the same now ?

		    EnvironmentSnapshot env = envModel.predictEnvironment(w.start);

		    // how much time is left in the window starting at w.start (or run start)
		    long rtw = tcwc.calculateRemainingTime(group, srcwin, start, env, hist);

		    // check to see if this is a critical one
		    int rn = tcwc.countRemainingNights(group, fullwin, w.start, env, hist);
		    
		    logger.log(1, "XT="+ ((double)xt/1000.0)+"s, RTW = "+
			       ((double)rtw/1000.0)+"s, ("+((double)rtw/60000.0)+"m RN="+rn);  
		    
		    // only count if the window is at least 0.5 minutes long
		    if (rtw > 30*1000L) {
			
			double gload = (double)xt/(double)(xt + rtw);
			
			// fill the bins in for w.start to w.end or end whichever is smallest
			long te = Math.min(end, w.end);
			int js = (int)(w.start - start)/binsize;
			int je = (int)(te - start)/binsize; // no need to fill beyond end of day.
			
			int nj = 0;
			for (int j = js; j<je; j++) {			
			    long t = start + binsize*j;
			    if (execModel.canDo(group, t, env, hist)) {
				load[j] += gload;
				if (rn == 1)
				    cload[j] += gload;
				pload[j] += gload*priorityFactor(group.getPriority());
				nj++;
			    }
			}
			logger.log(1, "Filled: bins from: "+js+" to "+je+", "+nj+" outof"+(je-js+1)+" poss total with "+gload);
			
		    }
		    
		}
		
	    } // next group
	    
	} // next prop
	    
	logger.log(1, "At "+ScheduleSimulator.sdf.format(new Date(time))+" Sum load: "+sumload);
	
	// Fill out the statistics
	DemandStatistics dstats = new DemandStatistics();

	int nav = 0;
	double sum = 0.0;
	double max = -1.0;
	double csum = 0.0;
	double cmax = -1.0;
	double psum = 0.0;
	double pmax = -1.0;
	for (int j = 0; j<nbin; j++) {
	    long tj = start + j*binsize;
	    logger.log(1,"Load ["+"] at "+ScheduleSimulator.sdf.format(new Date(tj))+" "+load[j]);
	    // generate a demand profile point (tj, l[j])
	    dstats.addPoint((double)tj, load[j]);
	    dstats.addCritPoint((double)tj, cload[j]);
	    dstats.addPriorityPoint((double)tj, pload[j]);
	    // should only include demand info for night-time in average
	    // i.e. any daytime stuff should be pruned out.
	    Position sun = Astrometry.getSolarPosition(tj);
	    double selev = sun.getAltitude(tj, site);
	    if (selev < Math.toRadians(0.0)) {		
		sum  += load[j];
		csum += cload[j];
		psum += pload[j];
		nav++;
	    } else {
		logger.log(1,"Sun is up: elev = "+Position.toDegrees(selev, 3));
	    }
	    if (load[j] > max)
		max = load[j];
	    if (cload[j] > cmax)
		cmax = cload[j];
	    if (pload[j] > pmax)
                pmax = pload[j];

	}

	dstats.average = sum/(double)nav;
	dstats.maximum = max;
	dstats.critAverage = csum/(double)nav;
	dstats.critMaximum = cmax;
	dstats.priorityAverage = psum/(double)nav;
	dstats.priorityMaximum = pmax;
	return dstats;

    }

    private double priorityFactor(int p) {
	switch (p) {
	case -2:
	    return 0.0;
	case -1:
	    return 0.6;
	case 0:
	case 1:
	    return 0.2;
	default:
	    return (double)p/5.0;
	}
    }
}
